/*
 * 日志器模块
 *
 * 通过日志器调用落地器输出日志，
 * 通过日志器建造器使用建造者模式创建日志器。
 * 通过日志器管理器管理所有的日志器。
 *
 * 两种日志器分别是同步日志器和异步日志器，
 * 同步日志器直接输出日志消息，异步日志器将日志消息对象交给异步工作器进行输出。
 *
 **/

#pragma once

#include <memory>
#include <vector>
#include <mutex>
#include <functional>
#include "util.hpp"
#include "sink.hpp"
#include "aswork.hpp"

namespace easylog
{
    class logger
    {
    public:
        struct fmt_with_loc;

        using ptr        = std::shared_ptr<logger>;
        using source_loc = util::source_loc;
        using fmt_str_t  = fmt_with_loc;
        using sinkers_t  = std::vector<sinker::ptr>;

        // put source_location and format_string as class members,
        // make it easy to define output api param.
        struct fmt_with_loc
        {
            fmt_with_loc(const char* s, source_loc l = source_loc()) : fmt(s), loc(l) {}
            fmt_with_loc(const std::string& s, source_loc l = source_loc()) : fmt(s), loc(l) {}
            std::string fmt;
            source_loc loc;
        };

    public:
        logger(const std::string& name, level::lv_enum level, const sinkers_t& sinkers)
            : _name(name)
            , _level(level)
            , _sinkers(sinkers)
            , _always_flh(false)
            , _flh_gap(util::time::t0sec)
            , _last_flh(util::time::now())
        {}

        virtual ~logger() {}

        const std::string& get_name() { return _name; }

        level::lv_enum get_level() { return _level; }

        void set_level(level::lv_enum lv) { _level = lv; }

        void set_name(const std::string& name) { _name = name; }

        const sinkers_t& get_sinkers() { return _sinkers; }

        void set_sinkers(const sinkers_t& sinkers) { _sinkers = sinkers; }

        void set_always_flush(bool flg = true) { _always_flh = flg; }

        void set_flush_every(time_t gap) { _flh_gap = gap; }

        virtual void flush() = 0;

        template<typename... Args> void debug(fmt_str_t fmt, Args&&... args) { dolog(fmt.loc, level::debug, fmt.fmt, std::forward<Args>(args)...); }
        template<typename... Args> void info (fmt_str_t fmt, Args&&... args) { dolog(fmt.loc, level::info , fmt.fmt, std::forward<Args>(args)...); }
        template<typename... Args> void warn (fmt_str_t fmt, Args&&... args) { dolog(fmt.loc, level::warn , fmt.fmt, std::forward<Args>(args)...); }
        template<typename... Args> void error(fmt_str_t fmt, Args&&... args) { dolog(fmt.loc, level::error, fmt.fmt, std::forward<Args>(args)...); }
        template<typename... Args> void fatal(fmt_str_t fmt, Args&&... args)
        {
            dolog(fmt.loc, level::fatal, fmt.fmt, std::forward<Args>(args)...);

#ifndef EASYLOG_NO_FATAL_TRACE
            flush();

            dolog(fmt.loc, level::fatal, util::tracer::stacktrace().c_str());
            for (auto& sinker : _sinkers)
                sinker->set_pattern("%m");
            flush();

            util::killer::exit(1);
#endif
        }

    protected:
        template<typename... Args>
        void dolog(const source_loc& loc, level::lv_enum lv, const std::string& fmt, Args&&... args)
        {
            if (lv < _level)
                return;

            log(message(lv, loc.file, loc.line, loc.function, _name.c_str(), \
                        util::payload::format(fmt, std::forward<Args>(args)...), _always_flh));
        }

        virtual void log(message&& msg) = 0;

    protected:
        std::string _name;
        level::lv_enum _level;
        sinkers_t _sinkers;
        std::mutex _mtx;
        std::atomic<bool> _always_flh;  // If flag is true, each log received by this logger must be flushed
        time_t _flh_gap; // flush time interval, if this flag is not zero, will flush every this interval.
        time_t _last_flh;
    };

    // synchronous logger
    // without any buffering, directly outputs logs to sinker
    class sync_logger : public logger
    {
    public:
        sync_logger(const std::string& name, level::lv_enum level, sinkers_t& sinkers)
            : logger(name, level, sinkers)
        {}

        virtual void log(message&& msg) override
        {
            std::unique_lock<std::mutex> lock(_mtx);

            // Check whether now is still in flush interval.
            // If not, set the msg to be flushed.
            if (_flh_gap != 0 && _last_flh + _flh_gap < util::time::now())
                msg.flush = true;

            for (auto& sinker : _sinkers)
            {
                // Can not use perfect forward here, which will transfer the memory of msg,
                // and the next sinker will not be able to obtain msg.
                sinker->sink(message(msg));

                if (msg.flush)
                    flush();
            }
        }

        virtual void flush() override
        {
            for (auto& sinker : _sinkers)
                sinker->flush();

            // After flush, we need to update `_last_flh`
            _last_flh = util::time::now();
        }
    };

    class async_logger : public logger
    {
    public:
        using value     = message;
        using buffer    = ring_queue<value>;
        using worker    = async_worker<buffer, value>;
        using work_type = worker::work_type;

    public:
        async_logger(const std::string& name,
                         level::lv_enum level,
                             sinkers_t& sinkers,
                              work_type type,
                                 size_t thnum,
                                 size_t qsize,
                                 size_t incr,
                                 size_t thres)
            : logger(name, level, sinkers)
            , _worker(new worker(type, thnum, qsize, incr, thres, \
                        std::bind(&async_logger::handler, this, std::placeholders::_1)))
        {}

        virtual void log(message&& msg) override
        {
            // push operator in worker is thread safed, no need to lock
            _worker->push(std::forward<message>(msg.set4parse()));
        }

        virtual void flush() override
        {
            // push a special message that only init the forflush member.
            _worker->push(std::forward<message>(message::_4flush));
        }

        void handler(buffer& buf)
        {
            value msg;

            {
                std::unique_lock<std::mutex> lock(_mtx);
                if (buf.rdable())
                    buf.pop(msg);
                else
                    return;
            }
            {
                std::unique_lock<std::mutex> lock(_hd_mtx); // Use hd_mtx instead of mtx,
                                                            // because must decouple data consumption and processing,
                                                            // to improve efficiency.
                // On macos we must wait until asworker is done,
                // otherwise we will see the exception below:
                //   lbc++abi: terminating with uncaught exception of type std::__1::system_error: mutex lock failed: Invalid argument

                if (_flh_gap != 0 && _last_flh + _flh_gap < util::time::now())
                    flush();
                // The reason why the judgment of flush time is placed in handler instead of log interface,
                // is that we do not want to block the business thread.

                if (msg.is4parse())
                {
                    for (auto& sinker : _sinkers)
                        sinker->sink(value(msg));
                }

                // If msg.flush is true or msg is a flush package, we all flush
                if (msg.is4flush() || msg.flush)
                {
                    for (auto& sinker : _sinkers)
                        sinker->flush();

                    _last_flh = util::time::now();
                }
            }
        }

        void set_thread_num(size_t num) { _worker->set_thread_num(num); }

    private:
        worker::ptr _worker;
        std::mutex _hd_mtx;
    };

    // Use the Builder pattern to construct logger.
    // It is divided into local logger builder and global logger builder.
    // The global builder will put logger in registry.
    class logger_builder
    {
    public:
        using ptr = std::shared_ptr<logger_builder>;
        using work_type = async_logger::work_type;

        enum logger_type
        {
            sync,
            async,
        };

    public:
        logger_builder() {}

        void build_logger_type(logger_type type) { _ltype = type; }

        void build_logger_name(const std::string& name) { _lname = name; }

        void build_limit_level(level::lv_enum lv = config::instance().get_level()) { _level = lv; }

        template<typename SinkType, typename... Args>
        void build_sinker(Args&&... args)
        {
            _sinkers.push_back(sinker_factory::create<SinkType>(std::forward<Args>(args)...));
        }

        void build_work_type(work_type type = \
                config::instance().get_work_type() ? \
                    work_type::unsafe : work_type::safe)
        {
            _wtype = type;
        }

        void build_thread_num(size_t num = config::instance().get_thread_num())
        {
            _thnum = num;
        }

        void build_buffer_size(size_t size = config::instance().get_buffer_size())
        {
            _qsize = util::size::tomsgnum(size);
        }

        void build_buffer_increment(size_t incre = config::instance().get_buffer_incr())
        {
            _incre = util::size::tomsgnum(incre);
        }

        void build_buffer_threshold(size_t thres = config::instance().get_buffer_thres())
        {
            _thres = util::size::tomsgnum(thres);
        }

        virtual logger::ptr build() = 0;

    protected:
        logger::ptr create()
        {
            if (_lname.empty())
            {
                throw except("build logger failed cause name is empty");
            }
            if (_sinkers.empty())
            {
                _sinkers.push_back(std::make_shared<stdout_sinker>());
            }
            if (_level == level::on)
            {
                throw except("build logger failed cause illegal level");
            }

            if (_ltype == sync)
            {
                return std::make_shared<sync_logger>(_lname, _level, _sinkers);
            }
            else if (_ltype == async)
            {
                return std::make_shared<async_logger>(_lname, _level, _sinkers, _wtype, _thnum, _qsize, _incre, _thres);
            }
            else
            {
                throw except("build logger need invoke build_logger_type function");
            }
        }

    protected:
        logger_type _ltype;
        std::string _lname;
        level::lv_enum _level;
        logger::sinkers_t _sinkers;

        work_type _wtype;
        size_t _thnum;
        size_t _qsize;
        size_t _incre;
        size_t _thres;
    };


    class local_logger_builder : public logger_builder
    {
    public:
        virtual logger::ptr build() override
        {
            return create();
        }
    };


    // registry is used to manage all loggers,
    // it provides interfaces for popping and inserting loggers,
    // protected by locks, making it thread-safe.
    class registry
    {
    public:
        static registry& instance()
        {
            static registry rsg;
            return rsg;
        }

        void add(const logger::ptr& lgr)
        {
            std::unique_lock<std::mutex> lock(_mtx);
            _registry[lgr->get_name()] = lgr;
        }

        logger::ptr get(const std::string& name)
        {
            std::unique_lock<std::mutex> lock(_mtx);

            if (!has(name))
                throw except("registry::get_logger: unknown logger");
            return _registry[name];
        }

        logger::ptr default_logger()
        {
            return _registry["default"];
        }

    private:
        bool has(const std::string& name)
        {
            return _registry.find(name) != _registry.end();
        }

        registry()
        {
            build_default_logger();
        }

        void build_default_logger()
        {
            logger_builder::ptr llb = std::make_shared<local_logger_builder>();
            llb->build_logger_name("default");
            llb->build_logger_type(logger_builder::logger_type::sync);
            llb->build_limit_level(config::instance().get_level());
            llb->build_sinker<stdout_sinker>();
            add(llb->build());
        }

        registry(const registry&) = delete;
        registry& operator=(const registry&) = delete;

    private:
        std::unordered_map<std::string, logger::ptr> _registry;
        std::mutex _mtx;
    };


    class global_logger_builder : public logger_builder
    {
    public:
        virtual logger::ptr build() override
        {
            logger::ptr lgr = create();
            registry::instance().add(lgr);
            return lgr;
        }
    };
}
